package top.z.template.parse;

import top.z.template.Source;

/**
 * 包装了 {@link Source} 内容, 并处理遍历其中的字符.
 * 通过 {@link #start} 和 {@link #slice} 方法来管理当前的 {@link Span}.
 */
public class CharacterStream {

    private final Source source;

    private int index = 0;
    private int total = 0;
    private int start = 0;

    public CharacterStream(Source source) {
        this(source, 0, source.getContent().length());
    }

    public CharacterStream(Source source, int index, int total) {
        if (index > total) {
            throw new IllegalArgumentException("Start must be <= total.");
        }
        if (index < 0) {
            throw new IndexOutOfBoundsException("Start must be >= 0.");
        }
        if (index > Math.max(0, source.getContent().length() - 1)) {
            throw new IndexOutOfBoundsException("Start outside of string.");
        }
        if (total > source.getContent().length()) {
            throw new IndexOutOfBoundsException("End outside of string.");
        }

        this.source = source;
        this.index = index;
        this.total = total;
    }

    /**
     * 返回流中是否还有字符
     */
    public boolean hasNext() {
        return index < total;
    }

    /**
     * 返回流中的下一个字符, 但不移动到下一个位置
     */
    public char peek() {
        if (hasNext()) {
            return source.getContent().charAt(index + 1);
        } else {
            throw new RuntimeException("No more characters in stream.");
        }
    }

    /**
     * 返回流中的下一个字符, 并且移动到下一个位置
     */
    public char consume() {
        if (hasNext()) {
            return source.getContent().charAt(index++);
        } else {
            throw new RuntimeException("No more characters in stream.");
        }
    }

    /**
     * 将给定的针(needle)与接下来的字符进行匹配, 如果匹配到且 consume 参数为true, 流的位置将根据针的长度向前移动
     */
    public boolean match(String needle, boolean consume) {
        int needleLength = needle.length();
        for (int i = 0, j = index; i < needleLength; i++, j++) {
            if (index >= total) {
                return false;
            }
            if (needle.charAt(i) != source.getContent().charAt(j)) {
                return false;
            }
        }
        if (consume) {
            index += needleLength;
        }
        return true;
    }

    /**
     * 返回下一个字符是否为数字
     */
    public boolean matchDigit(boolean consume) {
        if (index >= total) {
            return false;
        }
        char c = source.getContent().charAt(index);
        if (Character.isDigit(c)) {
            if (consume) {
                index++;
            }
            return true;
        }
        return false;
    }

    /**
     * 返回下一个字符是否是标识符的开始
     */
    public boolean matchIdentifierStart(boolean consume) {
        if (index >= total) {
            return false;
        }
        char c = source.getContent().charAt(index);
        // 是否是 Java 标识符的起始字符
        if (Character.isJavaIdentifierStart(c)) {
            if (consume) {
                index++;
            }
            return true;
        }
        return false;
    }

    /**
     * 返回下一个字符是否是标识符的开始, 并根据参数决定是否将其消费掉.
     */
    public boolean matchIdentifierPart(boolean consume) {
        if (index >= total) {
            return false;
        }
        char c = source.getContent().charAt(index);
        if (Character.isJavaIdentifierPart(c)) {
            if (consume) {
                index++;
            }
            return true;
        }
        return false;
    }

    /**
     * 跳过连续的空白字符.
     */
    public void skipWhiteSpace() {
        while (index < total) {
            char c = source.getContent().charAt(index);
            if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
                index++;
            } else {
                break;
            }
        }
    }

    /**
     * 在当前流位置启动新的 Span, 调用 {@link #slice} 以完成 Span
     */
    public void start() {
        start = index;
    }

    /**
     * 当前正在处理的文本
     */
    public String currentText() {
        return this.source.getContent().substring(start, index);
    }

    /**
     * 在当前流位置切割 Span 调用 {@link #start()}
     */
    public Span slice() {
        return new Span(source, start, index);
    }

    public boolean isEmpty() {
        return start == this.index;
    }

    public boolean notEmpty() {
        return start != this.index;
    }

    /**
     * 获取原始文本资源
     */
    public Source getSource() {
        return source;
    }

    /**
     * 返回流中的当前字符位置
     */
    public int getIndex() {
        return index;
    }

    /**
     * 返回流中的 Span 开始位置
     */
    public int getStart() {
        return start;
    }

    /**
     * 返回流中的总字符长度
     */
    public int getTotal() {
        return total;
    }
}
